home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / smaltalk / manchest.lha / MANCHESTER / usenet / st80_pre4 / ArrowedSpline.st < prev    next >
Text File  |  1993-07-24  |  10KB  |  283 lines

  1. "    NAME        ArrowedSpline 
  2.     AUTHOR        ross@prls.UUCP (Ross Morley)
  3.     FUNCTION Splines with arrowheads
  4.     ST-VERSIONS    2.3
  5.     PREREQUISITES     
  6.     CONFLICTS    
  7.     DISTRIBUTION      world
  8.     VERSION        1.1
  9.     DATE    5 Sep 90
  10. SUMMARY     This is a subclass of the standard ST80 library class Spline.
  11. It behaves just like Spline but adds arrowheads to the curves,
  12. indicating direction. The direction is determined by the order of the
  13. points you supply to define the curve. An arrowhead is placed at the
  14. midpoint of each section of the curve between adjacent pairs of
  15. defining points.
  16. "
  17. !
  18. "
  19. Newsgroups: comp.lang.smalltalk
  20. Subject: ArrowedSpline goody
  21. Keywords: spline paths graphics goodies
  22. Message-ID: <43620@prls.UUCP>
  23. Date: 5 Sep 90 01:04:37 GMT
  24. Organization: Philips R&D Center, Sunnyvale, CA
  25.  
  26. This is a subclass of the standard ST80 library class Spline. It behaves just
  27. like Spline but adds arrowheads to the curves, indicating direction. The
  28. direction is determined by the order of the points you supply to define the
  29. curve. An arrowhead is placed at the midpoint of each section of the curve
  30. between adjacent pairs of defining points.
  31.  
  32. Displaying the arrowheads is very fast - displaying an ArrowedSpline is not
  33. noticeably slower than displaying a Spline (most of the time goes into
  34. displaying the curve). Each ArrowedSpline instance caches Forms for the
  35. arrowheads at orientations 15 degrees apart around the circle (ie. 4 angles
  36. in each 45 degree sector). This can be changed to any integral number of
  37. steps per 45 degreee angle simply by changing a number in the class
  38. initialize method and executing the method. The provision of
  39. 'initializePoints' and 'initializePoints:' methods in the public interface
  40. allows you to reuse an instance, with a different set of points, without
  41. recomposing the cached arrowhead forms. The cached Forms are discarded when 
  42. the spline's form is changed, as it is then necessary to recompose them.
  43.  
  44. Try the example. Change some parameters in the example (eg. the form) and try
  45. it again. Have fun!!
  46.  
  47.  
  48. Ross P. Morley                                  pyramid!!prls!!ross
  49. Philips Research, Sunnyvale                     philabs!!prls!!ross
  50. 811 E. Arques Ave (MS02)            ross@prls.uucp
  51. Sunnyvale, CA 94088-3409                        Tel. (408) 991 5057
  52. "
  53.  
  54. 'From Smalltalk-80, Version 2.3 of 13 June 1988 on 4 September 1990 at 5:04:45 pm'!
  55.  
  56. Spline subclass: #ArrowedSpline
  57.     instanceVariableNames: 'arrows arrowBrush arrowHalfWidth '
  58.     classVariableNames: 'StepsPer45degreeAngle '
  59.     poolDictionaries: ''
  60.     category: 'Graphics-Paths'!
  61. ArrowedSpline comment:
  62. 'This is a Spline curve with arrowheads between each defining point (thus a Spline defined 
  63. with N points has N-1 arrowheads). The arrowheads point in the direction from the first to 
  64. the last point.
  65.  
  66. Instance variables:
  67.  
  68. arrows                An Array with cached arrowhead Forms at discrete angles around a full 
  69.                     circle. The number of Forms is 8*StepsPer45degreeAngle.
  70.  
  71. arrowBrush            The Form used to draw the arrowheads.
  72.  
  73. arrowHalfWidth        An Integer. No. of pixels from the center to any edge of an arrow Form.
  74.  
  75. Class variables:
  76.  
  77. StepsPerRightAngle        The number of discrete Forms cached per quarter circle.
  78.  
  79.                     Copyright (c) Ross P. Morley, September 1989.
  80.     This program is placed in the public domain. You may use and alter this program freely
  81.     for non-commercial purposes as long as you leave this message intact.  Neither I nor
  82.     my company will recognize any responsibility for damages arising from use of this program.
  83. '!
  84.  
  85.  
  86. !ArrowedSpline methodsFor: 'accessing'!
  87.  
  88. arrowForGradient: aPoint
  89.     "Answer the arrow Form whose gradient is closest to the gradient defined 
  90.     by aPoint whose x and y components are both Numbers and not both zero."
  91.  
  92.     ^self arrowFormAt: (self indexForGradient: aPoint)!
  93.  
  94. form: aForm 
  95.     "Make the argument, aForm, the receiver's form.
  96.     Compose a new arrowBrush and recompute the arrowHalfWidth.
  97.     Invalidate the cached arrowhead forms so they will be recomputed."
  98.  
  99.     | brushWidth |
  100.     super form: aForm.
  101.     brushWidth _ (self form width * 2 // 3) max: 1.
  102.     (arrowBrush _ Form extent: brushWidth asPoint) 
  103.         offset: (brushWidth // 2) negated asPoint; 
  104.         black.
  105.     arrowHalfWidth _ (self form width * 2.5) rounded.
  106.     self initializeArrowsArray!
  107.  
  108. initializePoints
  109.     "Reinitialize the collection of points so a new set can be added."
  110.  
  111.     self initializeCollectionOfPoints!
  112.  
  113. initializePoints: anInteger
  114.     "Reinitialize the collection of points so a new set can be added, specifying the initial size."
  115.  
  116.     self initializeCollectionOfPoints: anInteger! !
  117.  
  118. !ArrowedSpline methodsFor: 'displaying'!
  119.  
  120. displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger mask: aForm 
  121.     "Method for display of a Spline curve approximated by straight line segments."
  122.  
  123.     | segment steps a b c d t |
  124.     segment _ Line new.
  125.     segment form: self form.
  126.     segment beginPoint: self first.
  127.     1 to: self size-1 do:        "for each knot"
  128.         [:k | 
  129.             "taylor series coefficients"
  130.         d _ self at: k.
  131.         c _ (derivatives at: 1) at: k.
  132.         b _ ((derivatives at: 2) at: k) / 2.0.
  133.         a _ ((derivatives at: 3) at: k) / 6.0.
  134.             "arrowhead"
  135.         (self arrowForGradient: a * 0.75 + b + c)     "3at^2 + 2bt + c, for t=0.5"
  136.             displayOn: aDisplayMedium
  137.             at: a * 0.5 + b * 0.5 + c * 0.5 + d + aPoint     "at^3 + bt^2 + ct + d, for t=0.5"
  138.             clippingBox: clipRect
  139.             rule: anInteger
  140.             mask: aForm.
  141.             "guess stepping parameter"
  142.         steps _ ((derivatives at: 2) at: k) abs + ((derivatives at: 2) at: k+1) abs.
  143.         steps _ 5 max: (steps x + steps y) // 100.
  144.         1 to: steps - 1 do: 
  145.             [:j | 
  146.             t _ j asFloat / steps.
  147.             segment endPoint: a * t + b * t + c * t + d.    "at^3 + bt^2 + ct + d"
  148.             segment
  149.                 displayOn: aDisplayMedium
  150.                 at: aPoint
  151.                 clippingBox: clipRect
  152.                 rule: anInteger
  153.                 mask: aForm.
  154.             segment beginPoint: segment endPoint].
  155.         segment endPoint: (self at: k+1).
  156.         segment
  157.             displayOn: aDisplayMedium
  158.             at: aPoint
  159.             clippingBox: clipRect
  160.             rule: anInteger
  161.             mask: aForm.
  162.         segment beginPoint: segment endPoint]! !
  163.  
  164. !ArrowedSpline methodsFor: 'private'!
  165.  
  166. arrowFormAt: anInteger
  167.     "Answer the cached arrow Form at index anInteger, composing it if necessary."
  168.  
  169.     (arrows at: anInteger) isNil
  170.         ifTrue: [self composeArrowFormAt: anInteger].
  171.     ^arrows at: anInteger!
  172.  
  173. composeArrowFormAt: anInteger
  174.     "Compose the arrow Form at anInteger index in the cache array."
  175.  
  176.     | arrowForm quadrant index dx dy tip bb |
  177.     arrowForm _ Form extent: (2*arrowHalfWidth + 1) asPoint. 
  178.     quadrant _ anInteger-1 // self class stepsPerRightAngle.
  179.     index _ anInteger-1 \\ self class stepsPerRightAngle.
  180.     index <= self class stepsPer45degreeAngle
  181.         ifTrue: [
  182.             dx _ 0.71*arrowHalfWidth.    "1/(2 sqrt) factor for 45 degree rotation"
  183.             dy _ dx * index / self class stepsPer45degreeAngle]
  184.         ifFalse: [
  185.             dy _ 0.71*arrowHalfWidth.    "1/(2 sqrt) factor for 45 degree rotation"
  186.             dx _ dy * (self class stepsPerRightAngle - index) / self class stepsPer45degreeAngle].
  187.     quadrant = 1 ifTrue: [tip _ dx. dx _ dy negated. dy _ tip].
  188.     quadrant = 2 ifTrue: [dx _ dx negated. dy _ dy negated].
  189.     quadrant = 3 ifTrue: [tip _ dy. dy _ dx negated. dx _ tip].
  190.     bb _ BitBlt
  191.         destForm: arrowForm
  192.         sourceForm: arrowBrush
  193.         halftoneForm: nil
  194.         combinationRule: Form under
  195.         destOrigin: 0@0
  196.         sourceOrigin: 0@0
  197.         extent: arrowBrush extent
  198.         clipRect: arrowForm boundingBox.
  199.     tip _ arrowHalfWidth asPoint + (dx@dy) rounded.    "pull tip forward along axis (angles go to 27 deg.)"
  200.     bb drawFrom: tip     "draw arm to point arrowHalfWidth out at -135 degrees off axis from center"
  201.         to: arrowHalfWidth asPoint - ((dx-dy)@(dx+dy)) rounded.
  202.     bb drawFrom: tip      "draw arm to point arrowHalfWidth out at +135 degrees off axis from center"
  203.         to: arrowHalfWidth asPoint - ((dx+dy)@(dy-dx)) rounded.
  204.     arrowForm offset: self form relativeRectangle center - arrowHalfWidth.
  205.     arrows at: anInteger put: arrowForm!
  206.  
  207. indexForGradient: aPoint
  208.     "Answer the arrows array index (an Integer) for the arrow closest to the gradient defined 
  209.     by aPoint whose x and y components are both Numbers and not both zero."
  210.  
  211.     | dx dy tmp flipped |
  212.     dx _ aPoint x abs.
  213.     dy _ aPoint y abs.
  214.     (flipped _ dy > dx) ifTrue: [tmp_dx. dx_dy. dy_tmp].    "ensure dy<=dx"
  215.     tmp _ (self class stepsPer45degreeAngle * dy/dx) rounded.
  216.     flipped                 "map back to right angle sector"
  217.         ifTrue: [tmp _ self class stepsPerRightAngle - tmp].
  218.     aPoint x negative            "map to top semicircle"
  219.         ifTrue: [tmp _ 2 * self class stepsPerRightAngle - tmp].
  220.     aPoint y negative            "map to full circle"
  221.         ifTrue: [tmp _ 4 * self class stepsPerRightAngle - tmp].
  222.     ^ (tmp \\ (4 * self class stepsPerRightAngle)) + 1    "map to array index"!
  223.  
  224. initializeArrowsArray
  225.     "Create a new Array for caching arrowhead Forms."
  226.  
  227.     arrows _ Array new: 4 * self class stepsPerRightAngle! !
  228. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  229.  
  230. ArrowedSpline class
  231.     instanceVariableNames: ''!
  232.  
  233.  
  234. !ArrowedSpline class methodsFor: 'accessing'!
  235.  
  236. stepsPer45degreeAngle
  237.  
  238.     ^StepsPer45degreeAngle!
  239.  
  240. stepsPerRightAngle
  241.  
  242.     ^ 2 * self stepsPer45degreeAngle! !
  243.  
  244. !ArrowedSpline class methodsFor: 'class initialization'!
  245.  
  246. initialize
  247.     "(Re)initialize the class and invalidate the arrow caches of any instances."
  248.     "ArrowedSpline initialize"
  249.  
  250.     StepsPer45degreeAngle _ 4.
  251.     self allInstances do: [:anAS | anAS initializeArrowsArray]! !
  252.  
  253. !ArrowedSpline class methodsFor: 'examples'!
  254.  
  255. splineSample
  256.     "Designate points on the Path by clicking the red button.  Terminate 
  257.     by pressing any other button.  A curve will be displayed, through the 
  258.     selected points, using a square black form."
  259.  
  260.     "ArrowedSpline splineSample."
  261.  
  262.     | splineCurve aForm flag|
  263.     aForm _ Form new extent: 3@3.
  264.     aForm black.
  265.     splineCurve _ self new.
  266.     splineCurve form: aForm.
  267.     flag _ true.
  268.     [flag] whileTrue:
  269.         [Sensor waitButton.
  270.          Sensor redButtonPressed
  271.             ifTrue: 
  272.                 [splineCurve add: Sensor waitButton. 
  273.                  Sensor waitNoButton.
  274.                  aForm displayOn: Display at: splineCurve last]
  275.             ifFalse: [flag_false]].
  276.     splineCurve computeCurve.
  277.     splineCurve isEmpty 
  278.         ifFalse: [splineCurve displayOn: Display at: 0@0 rule: Form under.
  279.                 Sensor waitNoButton].
  280.      ^splineCurve! !
  281.  
  282. ArrowedSpline initialize!
  283.